package modelos;

import edu.bonn.cs.iv.bonnmotion.MobileNode;
import edu.bonn.cs.iv.bonnmotion.Position;
import edu.bonn.cs.iv.bonnmotion.RandomSpeedBase;
import edu.bonn.cs.iv.bonnmotion.Scenario;

public class ModeloDirecaoAleatoria extends RandomSpeedBase {

	private static final String MODEL_NAME = "ModeloDirecaoAleatoria";

	private double tempo = 0.0;
	
	public ModeloDirecaoAleatoria(int nodes, double x, double y, double duration, double ignore, long randomSeed, double minspeed, double maxspeed, double maxpause) {
		super(nodes, x, y, duration, ignore, randomSeed, minspeed, maxspeed, maxpause);
		generate();
	}

	public ModeloDirecaoAleatoria( String[] args ) {
		go(args);
	}

	public void go( String[] args ) {
		super.go(args);
		generate();
	}

	public void generate() {
		preGeneration();

		for (int i = 0; i < this.nodeCount(); i++) {
			node[i] = new MobileNode();

			double radianos = 0.0; 
			double speed = 0.0;
			Position pos = randomNextPosition();
			
			for (double t = 0.0; t < this.duration; t += tempo) {
				speed = (maxspeed - minspeed) * randomNextDouble() + minspeed;
				
				double angMin = calcAngMin(pos);
				double angMax = calcAngMax(pos);
				radianos = ((angMax - angMin) * randomNextDouble() + angMin) * 2 * Math.PI / 360;
				pos = getNextPosition(radianos, speed, pos);
				
				if (! (node[i].add(t, pos)))
					throw new RuntimeException(MODEL_NAME + ".go: error while adding waypoint");
				if ((t < duration) && (maxpause > 0.0)) {
					double pause = maxpause * randomNextDouble();
					t += pause;
				}
			} 
		}

		postGeneration();
	}

	
	/**
	 * Calcula o menor angulo que a proxima direcao pode ter.
	 * @param pos a posicao do MN
	 * @return o menor angulo
	 */
	private double calcAngMin(Position pos) {
		
		// borda inferior
		if (pos.y == 0.0) {
			return (pos.x == x) ?  90.0 : 0.0;
		}
		
		// borda direita
		if (pos.x == x) {
			return (pos.y == y) ? 180.0 : 90.0;
		}

		// borda superior
		if (pos.y == y) {
			return (pos.x == 0) ? 270.0 : 180;
		}
		
		// borda esquerda
		if (pos.x == 0) {
			return (pos.y == 0) ? 0.0 : 270;
		}
		
		return 0.0;
	}
	
	/**
	 * Calcula o maior angulo que a proxima direcao pode ter.
	 * @param pos a posicao do MN
	 * @return o maior angulo
	 */
	private double calcAngMax(Position pos) {
		
		// borda inferior
		if (pos.y == 0.0) {
			return (pos.x == 0.0) ?  90.0 : 180.0;
		}
		
		// borda direita
		if (pos.x == x) {
			return (pos.y == 0) ? 180.0 : 270.0;
		}

		// borda superior
		if (pos.y == y) {
			return (pos.x == x) ? 270.0 : 360;
		}
		
		// borda esquerda
		if (pos.x == 0) {
			return (pos.y == y) ? 360.0 : 450;
		}
		
		return 360.0;
	}	
	
	/**
	 * Calcula a proxima posicao
	 * @param angAtual angulo em radianos
	 * @param velAtual velocidade
	 * @param pAnt     posicao anterior
	 * @return a nova posicao do MN
	 */
	private Position getNextPosition(double angAtual, double velAtual, Position pAnt) {
		double x_ = 0.0, y_ = 0.0;

		boolean achou = false;
		
		double tempos[] = new double[4];
		tempos[0] = (0 - pAnt.x) / ( velAtual * Math.cos(angAtual)); // borda esq
		tempos[1] = (x - pAnt.x) / ( velAtual * Math.cos(angAtual)); // borda dir
		tempos[2] = (0 - pAnt.y) / ( velAtual * Math.sin(angAtual)); // borda inf
		tempos[3] = (y - pAnt.y) / ( velAtual * Math.sin(angAtual)); // borda sup
		
		int borda = -1; 
		for (int t = 0; t < tempos.length; t++) {
			if (tempos[t] > 0) {
				if (!achou) {
					tempo = tempos[t];
					borda = t;
					achou = true;
				} else {
					if (tempos[t] < tempo) {
						tempo = tempos[t];
						borda = t;
					}
				}
			}
		}
		x_ = pAnt.x + velAtual * Math.cos(angAtual) * tempo;
		y_ = pAnt.y + velAtual * Math.sin(angAtual) * tempo;
		
		switch (borda) {
			case 0:
				x_ = 0.0;
				break;
			case 1:
				x_ = x;
				break;
			case 2:
				y_ = 0.0;
				break;
			case 3:
				y_ = y;
				break;
		}
		
		return new Position(x_, y_);
	}

	public static void printHelp() {
		Scenario.printHelp();
		System.out.println( MODEL_NAME + ":" );
	}

}